/**
 * @preserve jPine Javascript tree control
 * http://code.google.com/p/jpine/
 *
 * Copyright 2011, Maxim Miroshnikov <miroshnikov at gmail dot com> 
 * jPine is licensed under the GNU General Public License
 * http://www.apache.org/licenses/LICENSE-2.0
*/

(function() {


jPine = function(target, items) {
    this.root = {id:0, 'name':null, 'parent':null, 'children':[], properties:{}, 'els':{'root':target} };
    this.items = [this.root];  //array of all items in tree
    this.insert(this.root, items); 
    this.expand(this.root);
}

jPine.prototype.renderer = {

    'create': function(tree,item) {
        var el = document.createElement('DIV');
        el.className = 'jpine';
        el.style.display = 'none';

        var oItem = document.createElement('DIV');
        oItem.className = 'item';
        el.appendChild(oItem);

        for (var p=item.parent; p.name; p=p.parent) {
            var d = document.createElement('SPAN');
            d.className = 'path';
            oItem.appendChild(d);
        }

        var oToggle = document.createElement('SPAN');
        oToggle.className = 'toggle';
        oToggle.onclick = function() { tree.toggle(item); };
        oItem.appendChild(oToggle);
        
        var oFolder = this.createFolder(tree,item,oItem);

        var oName = document.createElement('A');
        oName.href = '#';
        oName.onclick = function() { tree.onClick(item);return false; };
        oName.ondblClick = function() { tree.on2Click(item); };
        oName.onblur = function() { tree.onBlur(item); };
        oName.innerHTML = item.name;

        var oNameHolder = document.createElement('SPAN');
        oNameHolder.className = 'name';
        oNameHolder.appendChild(oName);
        oItem.appendChild(oNameHolder);

        var oVal = document.createElement('SPAN');
        oVal.className = 'value';
        oItem.appendChild(oVal);

        item.parent.els.root.appendChild(el);
        item.els = {
            'root':el, 
            'folder':oFolder, 
            'toggle':oToggle, 
            'name':oName, 
            'value':oVal
        };
    },

    'createFolder' : function(tree, item, oParent) {
        var oFolder = document.createElement('SPAN');
        oFolder.className = 'folder';
        oFolder.onclick = function() { tree.onFolderClick(item); };
        oFolder.ondblclick = function() { tree.onFolder2Click(item); };
        oParent.appendChild(oFolder);
        return oFolder;
    },

    'hide': function(item) {
        item.els.root.style.display = 'none';
    },

    'show': function(item) {
        item.els.root.style.display = 'block';
        if (item.needUpdate) {
            this.update(item);
        }
    },

    'update': function(item) {
        if (item.els.root.style.display == 'none') {
            item.needUpdate = true;
            return;
        }

        var oPath = item.els.toggle.previousSibling;
        for (var p=item.parent; p.name && oPath; p=p.parent, oPath=oPath.previousSibling) {
            oPath.className = p.nextSibling ? 'path dotted' : 'path';
        }

        var toggleClass = '';
        if (item.children.length) {
            toggleClass = item.expanded ? 'expanded' : 'collapsed';
        }
        if (!item.parent.name && !item.previousSibling) {
            toggleClass += 'first';
        }
        if (!item.nextSibling) {
            toggleClass += 'last';
        }
        item.els.toggle.className = 'toggle '+ toggleClass;

        this.updateFolder(item);
        this.updateName(item);
        
        item.els.value.innerHTML = ('value' in item.properties) ? item.properties.value : '';

        item.needUpdate = false;
    },
    
    'updateFolder' : function(item) {
        var cl = item.children.length ? (item.expanded ? 'open' : 'closed') : 'leaf';
        item.els.folder.className = 'folder '+ cl;
        if ('folderClass' in item.properties) {
            item.els.folder.className += ' ' + item.properties.folderClass;
        }
    },

    'updateName' : function(item) {
        item.els.name.innerHTML = item.name;
        if ('nameClass' in item.properties) {
            item.els.name.className = item.properties.nameClass;
        }
    },

    'clone' : function() {
        var ob = {};
        for (var prop in this) {
            ob[prop] = this[prop];
        }
        return ob;
    }
}


jPine.prototype.insert = function(parent, children) {
    if (children instanceof Object) {
        var prevItem = null;
        for (var nm in children) {
            if (!nm) {
                if (parent.name) {
                    var prop = children[nm];
                    if (prop instanceof Object) {
                        parent.properties = prop;
                    }
                    else {
                        parent.properties.value = prop;
                    }
                }
                continue;
            }
            var item = {
                'id' : this.items.length,
                'name' : nm,
                'parent' : parent,
                'children' : [],
                'properties': {},
                'expanded' : false,
                'needUpdate' : true
            };
            if (prevItem) {
                prevItem.nextSibling = item;
            }
            item.previousSibling = prevItem;
            prevItem = item;
            parent.children.push(item);
            this.items.push(item);
            this.renderer.create(this, item);
            this.insert(item, children[nm]);
        }
        if (parent.name) {
            this.renderer.update(parent);
        }
    }
}

jPine.prototype.remove = function(item) {
    item.parent.els.root.removeChild( item.els.root );

    if (item.previousSibling) {
        item.previousSibling.nextSibling = item.nextSibling;
        this.renderer.update(item.previousSibling);
    }
    if (item.nextSibling) {
        item.nextSibling.previousSibling = item.previousSibling;
        this.renderer.update(item.nextSibling);
    }

    for (var i=0; i<item.parent.children.length; ++i) {
        if (item.parent.children[i] == item) {
            item.parent.children.splice(i,1);
            break;
        }
    }
    this.renderer.update(item.parent);
}

jPine.prototype.expand = function(item) {
    if (!item) {  //expand top level
        for (var i in this.root.children) {
            this.expand(this.root.children[i]);
        }
        return;
    }
    if (item.expanded) {
        return;
    }
    var count = item.children.length;
    if (!count) {
        return;
    }
    item.expanded = true;
    for (var i=0; i<count; ++i) {
        this.renderer.show(item.children[i]);
    }
    if (item.parent) {
        this.renderer.update(item);
        this.expand(item.parent);
    }
}

jPine.prototype.expandAll = function(item) {
    var tree = this;
    this.forEach(function(i){tree.expand(i);}, item);
}

jPine.prototype.collapse = function(item) {
    if (!item) {  //collapse top level
        for (var i in this.root.children) {
            this.collapse(this.root.children[i]);
        }
        return;
    }
    if (!item.expanded) {
        return;
    }
    item.expanded = false;
    var count = item.children.length;
    for (var i=0; i<count; ++i) {
        this.renderer.hide(item.children[i]);
    }
    this.renderer.update(item);
}

jPine.prototype.collapseAll = function(item) {
    var tree = this;
    this.forEach(function(i){tree.collapse(i);}, item);
}

/** run f(item) for item and for every of item's descendants 
    @param f  callback function, return false to stop traversing immediately
    @param item  root item  (or the entire tree if not passed)
*/
jPine.prototype.forEach = function(f,item) {
    if (item) {
        if (false === f(item)) {
            return false;
        }
    } else {
        item = this.root;
    }

    for (var i=0; i<item.children.length; ++i) {
        if (false === this.forEach(f,item.children[i])) {
            return false;
        }
    }
    return true;
}

jPine.prototype.getStates = function(item) {
    item = item || this.root;
    var states = [];
    if (item.expanded) {
        states.push(item.id);
        for (var i=0; i<item.children.length; ++i) {
            states = states.concat( this.getStates(item.children[i]) );
        }
    }
    return states;
}

jPine.prototype.setStates = function(states) {
    for(var i=0; i<states.length; ++i) {
        this.expand(this.items[states[i]]);
    }
}

jPine.prototype.toggle = function(item) {
	item.expanded ? this.collapse(item) : this.expand(item);
	this.onToggle(item);
}

jPine.prototype.onToggle = function(item) {
}

jPine.prototype.onFolderClick = function(item) {
    this.onToggle(item);
}

jPine.prototype.onFolder2Click = function(item) {
}

jPine.prototype.onClick = function(item) {
}

jPine.prototype.on2Click = function(item) {
}

jPine.prototype.onBlur = function(item) {
}




jPineCheck = function(target, items) {
    this.ctor = jPine;
    this.ctor(target, items);
}
jPineCheck.prototype = new jPine;

jPineCheck.prototype.insert = function(parent, children) {
    jPine.prototype.insert.call(this,parent,children);
    if (parent.properties.checked) {
        this.updateCheckState(parent, false);        
    }
}

jPineCheck.prototype.getChecked = function() {
    var checkedItems = [];
    this.forEach(
        function (item) {
            if (item.properties.checked) {
                checkedItems.push(item);
            }
        }
    );
    return checkedItems;
}

jPineCheck.prototype.getPropertyOfChecked = function(propName) {
    var checkedItems = this.getChecked();
    var props = [];
    for (var i=0; i<checkedItems.length; ++i) {
        props.push( checkedItems[i].properties[propName] );
    }
	return props;
}

jPineCheck.prototype.renderer = jPine.prototype.renderer.clone();

jPineCheck.prototype.renderer.createFolder = function(tree, item, oParent) {
    var oFolder = document.createElement('SPAN');
    oFolder.onclick = function() { tree.doCheck(item) };
    oFolder.className = 'checkbox';
    oParent.appendChild(oFolder);
    return oFolder;
}

jPineCheck.prototype.renderer.updateFolder = function(item) {
    item.els.folder.className = 'checkbox';
    if (item.properties.checked) {
        item.els.folder.className += ' on';
    }
    if ('folderClass' in item.properties) {
        item.els.folder.className += ' ' + item.properties.folderClass;
    }
}

jPineCheck.prototype.renderer.updateName = function(item) {
    item.els.name.innerHTML = item.name;
    var clName = '';
    if (item.properties.checked) {
        clName += 'checked';
    }
    if (item.properties.hasCheckedDescendant) {
        clName += ' checkedDescendant';
    }
    item.els.name.className = clName + ' ' + item.properties.nameClass;
}

jPineCheck.prototype.doCheck = function(item) {
    item.properties.checked = !item.properties.checked;
    this.updateCheckState(item, false);
    this.onCheck(item);
}

jPineCheck.prototype.onCheck = function(item) {}

jPineCheck.prototype.updateCheckState = function(item, checked) {
    if (!item.name) {
        return;
    }
    checked = checked || this.hasCheckedDescendant(item);
    item.properties.hasCheckedDescendant = checked; 
    this.updateCheckState(item.parent, checked);
    this.renderer.update(item);
}

jPineCheck.prototype.hasCheckedDescendant = function(item) {
    for (var i=0; i<item.children.length; ++i) {
        if (item.children[i].properties.checked || this.hasCheckedDescendant(item.children[i])) {
            return true;
        }
    }
    return false;
}

jPineCheck.prototype.onClick = function(item) {
    this.doCheck(item);
}


})()